#include <Ape/DateTime.h>

#include <stdio.h>
#include <Ape/String.h>

#if defined(APE_USES_TIME_T)
#include <time.h>
#include <sys/time.h>
#endif

static const char* s_WeekDayNames[] = {
	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};

namespace Ape {
	DateTime::DateTime()
			: m_Year (1900),
			m_Month (1),
			m_MDay (1),
			m_Hour (0),
			m_Min (0),
			m_Sec (0),
			m_MiSec (0),
			m_TimeInfoIsSet (false)
	{
	}
	
	DateTime::DateTime (const DateTime& other)
			: Object (other),
			m_Year (other.m_Year),
			m_Month (other.m_Month),
			m_MDay (other.m_MDay),
			m_Hour (other.m_Hour),
			m_Min (other.m_Min),
			m_Sec (other.m_Sec),
			m_MiSec (other.m_MiSec),
			m_TimeInfoIsSet (other.m_TimeInfoIsSet),
			m_TimeInfo (other.m_TimeInfo)
	{
	}
	
	DateTime::~DateTime() {
	
	}
	
	UInt DateTime::get_Year() const {
		return m_Year;
	}
	UInt DateTime::get_Month() const {
		return m_Month;
	}
	UInt DateTime::get_MDay() const {
		return m_MDay;
	}
	UInt DateTime::get_Hour() const {
		return m_Hour;
	}
	UInt DateTime::get_Min() const {
		return m_Min;
	}
	UInt DateTime::get_Sec() const {
		return m_Sec;
	}
	UInt DateTime::get_MiSec() const {
		return m_MiSec;
	}
	
	void DateTime::set_Year (UInt v) {
		m_Year = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_Month (UInt v) {
		m_Month = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_MDay (UInt v) {
		m_MDay = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_Hour (UInt v) {
		m_Hour = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_Min (UInt v) {
		m_Min = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_Sec (UInt v) {
		m_Sec = v;
		m_TimeInfoIsSet = false;
	}
	void DateTime::set_MiSec (UInt v) {
		m_MiSec = v;
		m_TimeInfoIsSet = false;
	}
	
	
	UInt DateTime::get_WDay() const {
#if defined(APE_USES_TIME_T)
		bool valid = isValid();
		if (!valid)
			return 0;
		return (uint) (m_TimeInfo.tm_wday + 1);
#endif
	}
	
	bool DateTime::isValid() const {
		if (!m_TimeInfoIsSet) {
			m_TimeInfo.tm_year = (uint) m_Year - 1900;
			m_TimeInfo.tm_mon = (uint) m_Month - 1;
			m_TimeInfo.tm_mday = (uint) m_MDay;
			
			m_TimeInfo.tm_hour = (uint) m_Hour;
			m_TimeInfo.tm_min = (uint) m_Min;
			m_TimeInfo.tm_sec = (uint) m_Sec;
			
			m_UnixTime = mktime (&m_TimeInfo);
			m_TimeInfoIsSet = m_UnixTime != -1;
		}
		return m_TimeInfoIsSet;
	}
	
	const String DateTime::ToString() const {
		char buff[128];
		sprintf (buff, "%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.6d",
				 (uint) get_Year(), (uint) get_Month(), (uint) get_MDay(),
				 (uint) get_Hour(), (uint) get_Min(), (uint) get_Sec(), (uint) get_MiSec() );
		return String (buff);
	}
	
	DateTime DateTime::Current() {
		DateTime dt;
#if defined(APE_USES_TIME_T)
		struct timeval tv;
		struct timezone tz;
		gettimeofday (&tv, &tz);
		struct tm* timeInfo = localtime (&tv.tv_sec);
		
		dt.m_Year = timeInfo->tm_year + 1900;
		dt.m_Month = timeInfo->tm_mon + 1;
		dt.m_MDay = timeInfo->tm_mday;
		dt.m_Hour = timeInfo->tm_hour;
		dt.m_Min = timeInfo->tm_min;
		dt.m_Sec = timeInfo->tm_sec;
		dt.m_MiSec = tv.tv_usec;
		
#else
#warning No datetime implementation is selected
#endif
		
		return dt;
	}
	
	void DateTime::_Dump (bool verbose) const {
		Object::_Dump (verbose);
		
		DUMP_NON_V_TYPE;
		printf ("\t\t%.4d-%.2d-%.2d %.2d:%.2d:%.2d.%.6d (%s) \n",
				(uint) get_Year(), (uint) get_Month(), (uint) get_MDay(),
				(uint) get_Hour(), (uint) get_Min(), (uint) get_Sec(), (uint) get_MiSec(), getWDays() [get_WDay()-1]);
	}
	
	const char** DateTime::getWDays() const {
		return s_WeekDayNames;
	}
	
	DateTime DateTime::Parse (const String& sTime) {
		DateTime dt;
//       2009-07-26 22:51:43.267903
		if (
			sTime.get_Length() != 26
			|| sTime.At (4) != L'-'
			|| sTime.At (7) != L'-'
			|| sTime.At (10) != L' '
			|| sTime.At (13) != L':'
			|| sTime.At (16) != L':'
			|| sTime.At (19) != L'.'
		)
			throw EInvalidFormat (L"Cannot parse time");
			
		String sYear = sTime.Substr (4);
		String sMonth = sTime.Substr (2, 5);
		String sMDay = sTime.Substr (2, 8);
		String sHour = sTime.Substr (2, 11);
		String sMin = sTime.Substr (2, 14);
		String sSec = sTime.Substr (2, 17);
		String sMiSec = sTime.Substr (6, 20);
		
		dt.m_Year = UInt::Parse (sYear);
		dt.m_Month = UInt::Parse (sMonth);
		dt.m_MDay = UInt::Parse (sMDay);
		dt.m_Hour = UInt::Parse (sHour);
		dt.m_Min = UInt::Parse (sMin);
		dt.m_Sec = UInt::Parse (sSec);
		dt.m_MiSec = UInt::Parse (sMiSec);
		
		return dt;
	}
	
	time_t DateTime::ToUnixTime() const {
		isValid();
		return m_UnixTime;
	}
	
	// ----------------------------------------------------------- //
	
	DateDiff::DateDiff (const Int& seconds, const Int& microSeconds, bool negative)
			: m_Sec (seconds),
			m_MiSec (microSeconds),
			m_Negative (negative)
	{}
	DateDiff::DateDiff (const DateDiff& other)
			: Object (other),
			m_Sec (other.m_Sec),
			m_MiSec (other.m_MiSec),
			m_Negative (other.m_Negative)
	{}
	DateDiff::~DateDiff() {}
	
	const Int& DateDiff::get_Sec() const {
		return m_Sec;
	}
	const Int& DateDiff::get_MiSec() const {
		return m_MiSec;
	}
	
	void DateDiff::set_Sec (const Int& v) {
		m_Sec = v;
	}
	void DateDiff::set_MiSec (const Int& v) {
		m_MiSec = v;
	}
	void DateDiff::set_Negative (bool b) {
		m_Negative = b;
	}
	bool DateDiff::get_Negative() const {
		return m_Negative;
	}
	
	bool operator == (const DateTime& dt1, const DateTime& dt2) {
		return dt1.ToUnixTime() == dt2.ToUnixTime() && dt1.get_MiSec() == dt2.get_MiSec();
	}
	bool operator != (const DateTime& dt1, const DateTime& dt2) {
		return ! (dt1 == dt2);
	}
	const DateDiff operator - (const DateTime& dt1, const DateTime& dt2) {
		bool negative = false;
		int dUnixTime = dt1.ToUnixTime() - dt2.ToUnixTime();
		UInt miSec1 = dt1.get_MiSec();
		UInt miSec2 = dt2.get_MiSec();
		int dMicroSec = 0;
		
		if (dUnixTime > 0) {
			dMicroSec += miSec1;
			dMicroSec += 1000000 - miSec2;
		}
		else if (dUnixTime < 0) {
			dMicroSec += miSec2;
			dMicroSec += 1000000 - miSec1;
			dUnixTime = -dUnixTime;
			negative = true;
		}
		else {
			dMicroSec = miSec1 - miSec2;
			if (dMicroSec < 0) {
				dMicroSec = -dMicroSec;
				negative = true;
			}
		}
		
		int dSec = 0;
		if (dMicroSec >= 1000000) {
			dMicroSec -= 1000000;
			dSec = 1;
		}
		
		if (dUnixTime >= 0 && dMicroSec) {
			dUnixTime += dSec;
		}
		if (dUnixTime < 0) {
			dUnixTime -= dSec;
		}
		//printf("\n---\n%i - %i\n---\n", dUnixTime, dMicroSec);
		
		DateDiff dd (dUnixTime, dMicroSec, negative);
		return dd;
	}
	
	const String DateDiff::ToString() const {
		char buff[128];
		sprintf (buff, "%s%i.%.6d", m_Negative ? "-" : "", (int) m_Sec, (int) m_MiSec);
		return buff;
	}
	
	bool operator == (const DateDiff& dd1, const DateDiff& dd2) {
		return (
				   (dd1.get_Negative() == dd2.get_Negative() )
				   && ( (int) dd1.get_Sec() == dd2.get_Sec() )
				   && ( (int) dd1.get_MiSec() == dd2.get_MiSec() )
			   );
	}
	bool operator != (const DateDiff& dd1, const DateDiff& dd2) {
		return ! (dd1 == dd2);
	}
	
	const DateDiff operator - (const DateDiff& dd1, const DateDiff& dd2) {
		if (dd2.get_Negative() ) {
			DateDiff nDd2 (dd2);
			nDd2.set_Negative (false);
			return dd1 + nDd2;
		}
		bool negative = false;
		
		int ds1 = dd1.get_Sec();
		int ds2 = dd2.get_Sec();
		int dDs = ds1 - ds2;
		
		int dm1 = dd1.get_MiSec();
		int dm2 = dd2.get_MiSec();
		int dDm = dm1 - dm2;
		
		int minusSec = 0;
		if (dDm < 0) {
			minusSec = 1;
			dDm = 1000000 + dDm;
		}
		dDs -= minusSec;
		if (dDs < 0) {
			negative = true;
			dDs = -dDs;
		}
		
		return DateDiff (dDs, dDm, negative);
	}
	
	const DateDiff operator + (const DateDiff& dd1, const DateDiff& dd2) {
		int s = (int) dd1.get_Sec() + dd2.get_Sec();
		int m = (int) dd1.get_MiSec() + dd2.get_MiSec();
		bool n = false;
		
		if (m >= 1000000) {
			s += 1;
			m -= 1000000;
		}
		else if (m < 0) {
			s -= 1;
			m = 1000000 + m;
		}
		if (s < 0) {
			s = -s;
			n = true;
		}
		
		return DateDiff (s, m, n);
	}
	
	bool operator < (const DateTime& dt1, const DateTime& dt2) {
		return (dt1 - dt2).get_Negative();
	}
	bool operator > (const DateTime& dt1, const DateTime& dt2) {
		return ! (dt1 - dt2).get_Negative();
	}
	
	bool operator <= (const DateTime& dt1, const DateTime& dt2) {
		return ! (dt1 > dt2);
	}
	bool operator >= (const DateTime& dt1, const DateTime& dt2) {
		return ! (dt1 < dt2);
	}
	
	
}

